/**
 * This file is part of the eConference project and it is distributed under the 
 * terms of the MIT Open Source license.
 * 
 * The MIT License
 * Copyright (c) 2005 Collaborative Development Group - Dipartimento di Informatica, 
 *                    University of Bari, http://cdg.di.uniba.it
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this 
 * software and associated documentation files (the "Software"), to deal in the Software 
 * without restriction, including without limitation the rights to use, copy, modify, 
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 
 * permit persons to whom the Software is furnished to do so, subject to the following 
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies 
 * or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package it.uniba.di.cdg.jabber.internal;

import it.uniba.di.cdg.jabber.JabberBackend;
import it.uniba.di.cdg.smackproviders.TypingNotificationPacket;
import it.uniba.di.cdg.xcore.chat.IChatMessage;
import it.uniba.di.cdg.xcore.chat.IChatService;
import it.uniba.di.cdg.xcore.network.IBackend;
import it.uniba.di.cdg.xcore.network.INetworkBackendHelper;
import it.uniba.di.cdg.xcore.network.events.ITypingEventListener;
import it.uniba.di.cdg.xcore.network.events.TypingEvent;
import it.uniba.di.cdg.xcore.network.messages.IMessage;
import it.uniba.di.cdg.xcore.network.model.IBuddy;
import it.uniba.di.cdg.xcore.network.services.INetworkServiceContext;
import it.uniba.di.cdg.xcore.network.services.NetworkServiceException;

import java.util.ArrayList;
import java.util.List;

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.util.StringUtils;

/**
 * A Jabber/XMPP implementation of the chat service.
 * <p>
 * Note that this class relies on a custom SMACK provider which is implemented
 * in the source dir of <code>org.jivesoftware.smack</code> plug-in.
 */
public class JabberChatService implements IChatService, PacketListener,
		MessageListener {
	/**
	 * The chat context this service has been create with.
	 */
	private ChatContext context;

	/**
	 * Event listeners.
	 */
	private final List<IChatListener> listeners;

	/**
	 * Typing event listeners for this chat.
	 */
	private final List<ITypingEventListener> typingListeners;

	/**
	 * Jabber chat implementation.
	 */
	private Chat chat;

	private JabberBackend backend;

	private INetworkBackendHelper helper;

	private MessageListener typingPacketListener;

	// private IBackendEventListener connectionStatusListener = new
	// IBackendEventListener() {
	// public void onBackendEvent( IBackendEvent event ) {
	// if (event instanceof BackendStatusChangeEvent &&
	// event.getBackendId().equals( JabberBackend.ID)) {
	// BackendStatusChangeEvent e = (BackendStatusChangeEvent) event;
	// if (e.isOnline()) {
	// // Display message ...
	// } else {
	// // ...
	// }
	// }
	// }
	// };

	/**
	 * Create an XMPP chat service.
	 * 
	 * @param context
	 * @param backend
	 */
	public JabberChatService(ChatContext context, JabberBackend backend) {
		this.context = context;
		this.backend = backend;
		this.listeners = new ArrayList<IChatListener>();
		this.typingListeners = new ArrayList<ITypingEventListener>();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniba.di.cdg.xcore.chat.IChatService#initialize()
	 */
	public void open() {
		org.jivesoftware.smack.ChatManager chatMgr = backend.getConnection()
				.getChatManager();
		if (context.getHelloMessage() == null
				|| context.getHelloMessage().getThreadId() == null) {
			this.chat = chatMgr.createChat(XMPPUtils.cleanJid(context.getBuddyId()), this);
		} else {
			String threadId = context.getHelloMessage().getThreadId();
			this.chat = chatMgr
					.createChat(XMPPUtils.cleanJid(context.getBuddyId()), threadId, this);
		}

		// Grab incoming typing notification packets ...
		typingPacketListener = new MessageListener() {
			@Override
			public void processMessage(Chat arg0, Message msg) {
				final TypingNotificationPacket typingPacket = (TypingNotificationPacket) msg
						.getExtension(TypingNotificationPacket.ELEMENT_NAME,
								TypingNotificationPacket.ELEMENT_NS);
				if (typingPacket == null)
					return;
				else {					
					IBuddy buddy = backend.getRoster().getBuddy(msg.getFrom());
					typingPacket.setWho(buddy.getName());
				TypingEvent typingEvent = new TypingEvent(typingPacket.getWho());
				for (ITypingEventListener l : typingListeners)
					l.onTyping(typingEvent);
				}

			}
		};
		chat.addMessageListener( typingPacketListener );

		// this.chat.addMessageListener( this );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * it.uniba.di.cdg.xcore.network.services.IChatService#sendMessage(it.uniba
	 * .di.cdg.xcore.network.messages.IChatMessage)
	 */
	public void sendMessage(IChatMessage message)
			throws NetworkServiceException {
		try {
			chat.sendMessage(message.getText());
		} catch (XMPPException e) {
			throw new NetworkServiceException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * it.uniba.di.cdg.xcore.network.services.IChatService#addChatListener(it
	 * .uniba.di.cdg.xcore.network.services.IChatService.IListener)
	 */
	public void addChatListener(IChatListener listener) {
		listeners.add(listener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * it.uniba.di.cdg.xcore.network.services.IChatService#removeChatListener
	 * (it.uniba.di.cdg.xcore.network.services.IChatService.IListener)
	 */
	public void removeChatListener(IChatListener listener) {
		listeners.remove(listener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.jivesoftware.smack.PacketListener#processPacket(org.jivesoftware.
	 * smack.packet.Packet)
	 */
	public void processPacket(Packet packet) {
		// It isn't a typing event so move on ...
		IMessage message = backend.convertFromSmack((Message) packet);
		if (message == null)
			return;

		for (IChatListener l : listeners)
			l.messageReceived(message);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniba.di.cdg.xcore.network.events.ITypingListener#typing()
	 */
	public void typing() {
		TypingNotificationPacket notification = new TypingNotificationPacket();
		notification.setWho(StringUtils.escapeForXML(getLocalUserId()));

		Message msg = new Message();
		msg.addExtension(notification);

		try {
			chat.sendMessage(msg);
		} catch (XMPPException e) {
			e.printStackTrace();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniba.di.cdg.xcore.network.services.IChatService#getContext()
	 */
	public ChatContext getContext() {
		return context;
	}

	public void setContext(INetworkServiceContext context) {
		this.context = (ChatContext) context;
	}
	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniba.di.cdg.xcore.network.services.IChatService#close()
	 */
	public void close() {
		// Stop receiving typing notifications
		if (backend.getConnection() != null) // we may have got disconnected
			chat.removeMessageListener(typingPacketListener);
		typingPacketListener = null;
		// See
		// http://www.jivesoftware.org/community/thread.jspa?messageID=95143&#95143
		listeners.clear();
		typingListeners.clear();
		chat = null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniba.di.cdg.xcore.chat.IChatService#getLocalUserId()
	 */
	public String getLocalUserId() {
		return backend.getUserAccount().getId() + "@"
				+ backend.getServerContext().getServerHost();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * it.uniba.di.cdg.xcore.chat.IChatService#addTypingEventListener(it.uniba
	 * .di.cdg.xcore.network.events.ITypingEventListener)
	 */
	public void addTypingEventListener(ITypingEventListener listener) {
		typingListeners.add(listener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * it.uniba.di.cdg.xcore.chat.IChatService#removeTypingEventListener(it.
	 * uniba.di.cdg.xcore.network.events.ITypingEventListener)
	 */
	public void removeTypingEventListener(ITypingEventListener listener) {
		typingListeners.remove(listener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * it.uniba.di.cdg.xcore.chat.IChatService#setHelper(it.uniba.di.cdg.xcore
	 * .network.INetworkBackendHelper)
	 */
	public void setHelper(INetworkBackendHelper helper) {
		this.helper = helper;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniba.di.cdg.xcore.chat.IChatService#getHelper()
	 */
	public INetworkBackendHelper getHelper() {
		return helper;
	}

	@Override
	public void processMessage(Chat arg0, Message msg) {
		// It isn't a typing event so move on ...
		IMessage message = backend.convertFromSmack(msg);
		if (message == null)
			return;

		for (IChatListener l : listeners)
			l.messageReceived(message);
	}

	@Override
	public IBackend getBackend() {
		return backend;
	}

	@Override
	public void setBackend(IBackend backend) {
		this.backend = (JabberBackend) backend;
		
	}
}
